/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package parser.analyzer

import scala.collection.mutable.{Map => MMap}
import parser.DataTypes._
import parser.Nodes._

sealed trait VariableType
case object VariableTypeGlobal extends VariableType
case object VariableTypeStatic extends VariableType
case object VariableTypeLocal  extends VariableType

case class VariableInfo(dataType: DataType, position: Int, varType: VariableType)

/**
 * @param g Initial value of global variables.
 */
class VariableTable(typeMap: Map[Int, DataType], g: TraversableOnce[(String, DataType)]) {
  type VarTable = MMap[String, VariableInfo]

  var globals: VarTable = MMap() ++ g.toSeq.zipWithIndex.map { case ((k, v), n) => (k, VariableInfo(v, n, VariableTypeGlobal)) }
  var nGlobals = globals.size

  var locals: List[VarTable] = List(MMap())
  var nLocals = 0
  var nStatic = 0

  /**
   * Finds variable.
   * @param n The taget variable name 
   *
   * @return When variable has been found, returns Some(VariableInfo), otherwise returns None.
   */
  def findVariable(n: String): Option[VariableInfo] = {
    def f(t: List[VarTable]): Option[VariableInfo] = {
      t match {
        case x::xs => x.get(n).orElse { f(xs) }
        case _ => None
      }
    }
    f(locals ++ List(globals)) 
  }

  /**
   * Finds type converter from 'from' type to 'to' type.
   * @param from Source type of convertion
   * @param to   Destion type of convertion
   *
   * @return When converter has been found returns function name and function type, 
   *         otherwise returns None.
   */
  def findConverter(from: DataType, to: DataType): Option[Expr] = {
    resolveType(typeMap, to) match {
      case NamedType(n) => findFunction(n, List(from)) match {
        case Left(func) if (matchType(typeMap, func.retType, to)) => Some(BuildInFunction(n, func))
        case _                                                    => None
      }
      case _ => None
    }
  }

  /**
   * Finds function.
   * @param name The target function name.
   * @param to   The target function's arguments type.
   *
   * @return When variable has been found, returns function type wrapped by Left, 
   *         otherwise error message wrapped by Right.
   */
  def findFunction(name: String, argsType: List[DataType]): Either[FunctionType, String] = {
    this.findVariable(name) match {
      case Some(VariableInfo(FunctionList(m), _, _)) => 
        m.find { x => matchTypeList(typeMap, x.argsType, argsType) } match {
          case Some(ft) => Left(ft)
          case None => Right("Invalid function type to %s(%s)".format(name, argsType.mkString(", ")))
        }
      case _ => Right("\"%s\" is undeclared function".format(name))
    }
  }

  private def registerVar(table: VarTable, 
                          name: String, 
                          dataType: DataType, 
                          position: Option[Int],
                          vt: VariableType) :Either[Int, String] = {

    if (table.contains(name)) {
      return Right("Variable \"%s\" has already defined.".format(name))
    }
    val pos: Int = position match {
      case Some(x) => x
      case None => vt match {
        case VariableTypeGlobal => try nGlobals finally nGlobals += 1
        case VariableTypeStatic => try nStatic  finally nStatic  += 1
        case VariableTypeLocal  => try nLocals  finally nLocals  += 1
      }
    }

    table(name) = VariableInfo(dataType, pos, vt)
    return Left(pos)
  }

  /**
   * Registers local variable.
   * 
   * @return If it failed, returns error message wrapped by Right.
   *         If it is successful and isStatic is true,  returns the position
   *         of variable registerred in static variable table wrapped by Some 
   *         wrapped by Left.
   *         If it is successful and isStatic is false, returns None wrapped 
   *         by Left.
   */
  def registerLocalVar(n: String, t: DataType, isStatic: Boolean): Either[Int, String] = {
    require(locals.length > 0)
    registerVar(locals.head, n, t, None, if (isStatic) VariableTypeStatic else VariableTypeLocal)
  }

  def registerStaticVar(n: String, t: DataType, pos: Int): Either[Int, String] = {
    require(locals.length > 0)
    registerVar(locals.head, n, t, Some(pos), VariableTypeStatic)
  }

  def registerGlobalVar(n: String, t: DataType) = {
    registerVar(globals, n, t, None, VariableTypeGlobal)
  }

  /**
   * Registers a function.
   *
   * @return If it fails, returns an error message wrapped by Some.
   */
  def registerFunction(name: String, f: FunctionType): Option[String] = {
    findVariable(name) match {
      case Some(VariableInfo(FunctionList(l), pos, _)) => 
        if (l.exists(_.argsType == f.argsType)) {
          Some("%s(%s) has already been defined".format(name, f.argsType.mkString(", ")))
        } else {
          globals(name) = VariableInfo(FunctionList(f::l), pos, VariableTypeGlobal)
          None
        }
      case None => 
        registerGlobalVar(name, FunctionList(List(f))) match {
          case Left(_)  => None // ignore position
          case Right(x) => Some(x)
        }
      case _ => 
        Some("%s has already been defined as variable.".format(name))
    }
  }

  def pushBlock(): Unit = {
    locals = locals.::(MMap())
  }

  def popBlock(): Unit = {
    require(locals.nonEmpty)
    locals = locals.tail
  }
}
